"""
User-Agent过滤中间件
检测和阻止恶意爬虫、机器人和自动化工具，提升API安全性
"""

import re
import time
from typing import List, Set, Dict, Pattern, Optional
from fastapi import Request, status
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware

from bot_api_v1.app.core.config import settings
from bot_api_v1.app.core.logger import logger


class UserAgentFilterMiddleware(BaseHTTPMiddleware):
    """
    User-Agent过滤中间件
    
    功能特性：
    - 检测和阻止已知的恶意爬虫和机器人
    - 识别自动化工具和脚本
    - 支持正则表达式模式匹配
    - 白名单和黑名单管理
    - 可疑User-Agent行为分析
    - 支持开发环境的调试模式
    """
    
    def __init__(self, app):
        super().__init__(app)
        
        # 配置参数
        self.config = {
            # 基础配置
            'enabled': getattr(settings, 'USER_AGENT_FILTER_ENABLED', True),
            'strict_mode': getattr(settings, 'USER_AGENT_FILTER_STRICT_MODE', False),
            'block_empty_ua': getattr(settings, 'BLOCK_EMPTY_USER_AGENT', False),
            'case_sensitive': getattr(settings, 'USER_AGENT_CASE_SENSITIVE', False),
            
            # 需要检查的路径模式
            'check_patterns': [
                '/api/',           # 所有API请求
                '/media/',         # 媒体处理请求
                '/wechat/',        # 微信相关请求
            ],
            
            # 豁免检查的路径
            'exempt_patterns': [
                '/static/',        # 静态文件
                '/docs',           # API文档
                '/redoc',          # ReDoc文档
                '/openapi.json',   # OpenAPI规范
                '/monitoring/health',  # 健康检查
                '/robots.txt',     # SEO文件
                '/sitemap.xml',    # 站点地图
                '/favicon.ico',    # 图标
            ],
            
            # HTTP方法配置
            'check_methods': {'GET', 'POST', 'PUT', 'PATCH', 'DELETE'},
            'exempt_methods': {'OPTIONS', 'HEAD'},
        }
        
        # 加载User-Agent规则
        self.blocked_patterns = self._load_blocked_patterns()
        self.allowed_patterns = self._load_allowed_patterns()
        self.suspicious_patterns = self._load_suspicious_patterns()
        
        logger.info(f"UserAgentFilterMiddleware initialized with {len(self.blocked_patterns)} blocked patterns")
    
    def _load_blocked_patterns(self) -> List[Pattern]:
        """加载被阻止的User-Agent模式"""
        patterns = []
        
        # 从配置加载自定义阻止模式
        config_patterns = getattr(settings, 'BLOCKED_USER_AGENT_PATTERNS', [])
        
        # 默认的恶意爬虫和机器人模式
        default_blocked = [
            # 常见爬虫和机器人
            r'(?i).*bot(?!tomob|mob).*',  # 各种bot，排除legitimate的mobile bot
            r'(?i).*crawler.*',
            r'(?i).*spider.*',
            r'(?i).*scraper.*',
            r'(?i).*harvester.*',
            
            # 自动化工具
            r'(?i).*curl.*',
            r'(?i).*wget.*',
            r'(?i).*python.*',
            r'(?i).*requests.*',
            r'(?i).*httpie.*',
            r'(?i).*postman.*',
            r'(?i).*insomnia.*',
            
            # 恶意工具
            r'(?i).*scanner.*',
            r'(?i).*exploit.*',
            r'(?i).*attack.*',
            r'(?i).*hack.*',
            r'(?i).*injection.*',
            
            # 空或极短的User-Agent
            r'^$',  # 完全空的
            r'^.{1,3}$',  # 长度小于等于3的
            
            # 可疑的简单字符串
            r'^[a-z]{1,5}$',  # 只有几个小写字母
            r'^[0-9]{1,5}$',  # 只有数字
            r'^test.*',       # 测试相关
            r'^demo.*',       # 演示相关
            
            # 已知的恶意User-Agent
            r'(?i).*sqlmap.*',
            r'(?i).*nmap.*',
            r'(?i).*nikto.*',
            r'(?i).*masscan.*',
            r'(?i).*zmap.*',
            r'(?i).*gobuster.*',
            r'(?i).*dirbuster.*',
            r'(?i).*burp.*',
            r'(?i).*owasp.*',
            
            # 批量下载工具
            r'(?i).*downloader.*',
            r'(?i).*fetcher.*',
            r'(?i).*grabber.*',
        ]
        
        # 合并配置的模式和默认模式
        all_patterns = config_patterns + default_blocked
        
        # 编译正则表达式
        for pattern_str in all_patterns:
            try:
                flags = re.IGNORECASE if not self.config['case_sensitive'] else 0
                pattern = re.compile(pattern_str, flags)
                patterns.append(pattern)
            except re.error as e:
                logger.error(f"Invalid User-Agent pattern: {pattern_str}, error: {e}")
        
        return patterns
    
    def _load_allowed_patterns(self) -> List[Pattern]:
        """加载允许的User-Agent模式（白名单）"""
        patterns = []
        
        # 从配置加载
        config_patterns = getattr(settings, 'ALLOWED_USER_AGENT_PATTERNS', [])
        
        # 默认允许的合法浏览器和应用
        default_allowed = [
            # 主流浏览器
            r'(?i).*Mozilla.*Firefox.*',
            r'(?i).*Mozilla.*Chrome.*',
            r'(?i).*Mozilla.*Safari.*',
            r'(?i).*Mozilla.*Edge.*',
            r'(?i).*Opera.*',
            
            # 移动端浏览器
            r'(?i).*Mobile.*Safari.*',
            r'(?i).*Chrome.*Mobile.*',
            r'(?i).*Samsung.*Browser.*',
            
            # 微信相关（项目需要）
            r'(?i).*MicroMessenger.*',
            r'(?i).*WeChat.*',
            
            # 合法的搜索引擎爬虫
            r'(?i).*Googlebot.*',
            r'(?i).*Bingbot.*',
            r'(?i).*Slurp.*',  # Yahoo
            r'(?i).*DuckDuckBot.*',
            r'(?i).*Baiduspider.*',
            
            # 社交媒体爬虫
            r'(?i).*facebookexternalhit.*',
            r'(?i).*Twitterbot.*',
            r'(?i).*LinkedInBot.*',
            
            # 监控和正常工具
            r'(?i).*UptimeRobot.*',
            r'(?i).*Pingdom.*',
            r'(?i).*StatusCake.*',
        ]
        
        # 开发环境额外允许的模式
        if settings.ENVIRONMENT == "development":
            dev_allowed = [
                r'(?i).*insomnia.*',
                r'(?i).*postman.*',
                r'(?i).*httpie.*',
                r'(?i).*curl.*',
                r'(?i).*python.*requests.*',
                r'.*test.*',
            ]
            default_allowed.extend(dev_allowed)
        
        all_patterns = config_patterns + default_allowed
        
        # 编译正则表达式
        for pattern_str in all_patterns:
            try:
                flags = re.IGNORECASE if not self.config['case_sensitive'] else 0
                pattern = re.compile(pattern_str, flags)
                patterns.append(pattern)
            except re.error as e:
                logger.error(f"Invalid allowed User-Agent pattern: {pattern_str}, error: {e}")
        
        return patterns
    
    def _load_suspicious_patterns(self) -> List[Pattern]:
        """加载可疑User-Agent模式（需要额外关注但不直接阻止）"""
        patterns = []
        
        suspicious_patterns = [
            # 异常长度的User-Agent
            r'.{500,}',  # 超过500字符的
            
            # 包含脚本语言特征
            r'(?i).*node.*js.*',
            r'(?i).*php.*',
            r'(?i).*perl.*',
            r'(?i).*ruby.*',
            
            # 包含版本号异常
            r'.*[Vv]ersion.*0\.0.*',
            r'.*1\.0.*test.*',
            
            # 可疑的自定义User-Agent
            r'.*MyApp.*',
            r'.*Custom.*',
            r'.*API.*Client.*',
            
            # 包含IP地址或主机名
            r'.*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}.*',
            r'.*localhost.*',
            r'.*127\.0\.0\.1.*',
        ]
        
        # 编译正则表达式
        for pattern_str in suspicious_patterns:
            try:
                flags = re.IGNORECASE if not self.config['case_sensitive'] else 0
                pattern = re.compile(pattern_str, flags)
                patterns.append(pattern)
            except re.error as e:
                logger.error(f"Invalid suspicious User-Agent pattern: {pattern_str}, error: {e}")
        
        return patterns
    
    async def dispatch(self, request: Request, call_next):
        """主要的请求处理逻辑"""
        
        # 检查中间件是否启用
        if not self.config['enabled']:
            return await call_next(request)
        
        # 获取请求信息
        path = request.url.path
        method = request.method.upper()
        
        # 检查是否需要进行User-Agent验证
        if not self._should_check_user_agent(path, method):
            return await call_next(request)
        
        # 获取User-Agent头
        user_agent = request.headers.get('User-Agent', '').strip()
        
        # 检查User-Agent
        check_result = self._validate_user_agent(user_agent, request)
        
        if check_result['action'] == 'block':
            # 记录阻止事件
            await self._log_user_agent_event(request, user_agent, check_result, 'blocked')
            
            # 返回拒绝响应
            return self._create_blocked_response(check_result['reason'])
        
        elif check_result['action'] == 'suspicious':
            # 记录可疑事件但允许继续
            await self._log_user_agent_event(request, user_agent, check_result, 'suspicious')
        
        # User-Agent检查通过或仅标记可疑，继续处理请求
        return await call_next(request)
    
    def _should_check_user_agent(self, path: str, method: str) -> bool:
        """判断是否需要检查User-Agent"""
        
        # 检查HTTP方法
        if method in self.config['exempt_methods']:
            return False
        
        if self.config['strict_mode'] and method not in self.config['check_methods']:
            return False
        
        # 检查豁免路径
        for pattern in self.config['exempt_patterns']:
            if path.startswith(pattern):
                return False
        
        # 检查需要验证的路径
        if self.config['check_patterns']:
            return any(path.startswith(pattern) for pattern in self.config['check_patterns'])
        
        # 默认需要检查
        return True
    
    def _validate_user_agent(self, user_agent: str, request: Request) -> dict:
        """验证User-Agent的有效性"""
        
        # 处理空User-Agent
        if not user_agent:
            if self.config['block_empty_ua']:
                return {
                    'action': 'block',
                    'reason': 'empty_user_agent',
                    'message': 'Empty User-Agent is not allowed'
                }
            else:
                return {
                    'action': 'allow',
                    'reason': 'empty_user_agent_allowed'
                }
        
        # 检查白名单（优先级最高）
        for pattern in self.allowed_patterns:
            if pattern.search(user_agent):
                return {
                    'action': 'allow',
                    'reason': 'whitelisted',
                    'matched_pattern': pattern.pattern
                }
        
        # 检查黑名单
        for pattern in self.blocked_patterns:
            if pattern.search(user_agent):
                return {
                    'action': 'block',
                    'reason': 'blacklisted',
                    'message': f'User-Agent matches blocked pattern',
                    'matched_pattern': pattern.pattern
                }
        
        # 检查可疑模式
        for pattern in self.suspicious_patterns:
            if pattern.search(user_agent):
                return {
                    'action': 'suspicious',
                    'reason': 'suspicious_pattern',
                    'message': f'User-Agent matches suspicious pattern',
                    'matched_pattern': pattern.pattern
                }
        
        # 额外的启发式检查
        heuristic_result = self._heuristic_check(user_agent)
        if heuristic_result['action'] != 'allow':
            return heuristic_result
        
        # 默认允许
        return {
            'action': 'allow',
            'reason': 'no_match_default_allow'
        }
    
    def _heuristic_check(self, user_agent: str) -> dict:
        """启发式User-Agent检查"""
        
        # 检查长度异常
        if len(user_agent) > 1000:
            return {
                'action': 'suspicious',
                'reason': 'excessive_length',
                'message': f'User-Agent too long: {len(user_agent)} characters'
            }
        
        if len(user_agent) < 10:
            return {
                'action': 'suspicious',
                'reason': 'too_short',
                'message': f'User-Agent too short: {len(user_agent)} characters'
            }
        
        # 检查字符异常
        # 检查是否包含过多的特殊字符
        special_chars = sum(1 for c in user_agent if not c.isalnum() and c not in ' .-_()[]{}/')
        if special_chars > len(user_agent) * 0.3:  # 超过30%是特殊字符
            return {
                'action': 'suspicious',
                'reason': 'excessive_special_chars',
                'message': f'Too many special characters: {special_chars}/{len(user_agent)}'
            }
        
        # 检查重复字符模式
        if len(set(user_agent.lower())) < len(user_agent) * 0.3:  # 独特字符少于30%
            return {
                'action': 'suspicious',
                'reason': 'repetitive_pattern',
                'message': 'User-Agent contains repetitive patterns'
            }
        
        # 检查是否只包含ASCII字符（简单检查）
        try:
            user_agent.encode('ascii')
        except UnicodeEncodeError:
            return {
                'action': 'suspicious',
                'reason': 'non_ascii_chars',
                'message': 'User-Agent contains non-ASCII characters'
            }
        
        return {
            'action': 'allow',
            'reason': 'heuristic_pass'
        }
    
    async def _log_user_agent_event(self, request: Request, user_agent: str, check_result: dict, action: str):
        """记录User-Agent事件"""
        
        client_ip = self._get_client_ip(request)
        
        # 根据严重程度决定日志级别
        if action == 'blocked':
            # 被阻止的请求，使用error级别
            logger.error(
                f"User-Agent过滤阻止: {check_result['reason']} - IP: {client_ip}, Path: {request.url.path}",
                extra={
                    "event_type": "user_agent_blocked",
                    "client_ip": client_ip,
                    "path": str(request.url.path),
                    "method": request.method,
                    "user_agent": user_agent[:500],  # 限制长度避免日志过长
                    "reason": check_result['reason'],
                    "message": check_result.get('message'),
                    "matched_pattern": check_result.get('matched_pattern'),
                    "action": action,
                    "severity": "high"
                }
            )
        else:
            # 可疑但未阻止的请求，使用info_to_db
            logger.info_to_db(
                f"User-Agent可疑行为: {check_result['reason']} - IP: {client_ip}, Path: {request.url.path}",
                extra={
                    "event_type": "user_agent_suspicious",
                    "client_ip": client_ip,
                    "path": str(request.url.path),
                    "method": request.method,
                    "user_agent": user_agent[:500],
                    "reason": check_result['reason'],
                    "message": check_result.get('message'),
                    "matched_pattern": check_result.get('matched_pattern'),
                    "action": action,
                    "severity": "medium"
                }
            )
    
    def _get_client_ip(self, request: Request) -> str:
        """获取真实客户端IP"""
        forwarded_ips = [
            request.headers.get("X-Forwarded-For"),
            request.headers.get("X-Real-IP"),
            request.headers.get("CF-Connecting-IP"),
            request.headers.get("X-Client-IP"),
        ]
        
        for ip in forwarded_ips:
            if ip:
                return ip.split(',')[0].strip()
        
        return request.client.host if request.client else "unknown"
    
    def _create_blocked_response(self, reason: str) -> JSONResponse:
        """创建阻止访问响应"""
        
        # 根据不同原因返回不同的错误信息
        error_messages = {
            'empty_user_agent': 'Access denied: User-Agent header is required',
            'blacklisted': 'Access denied: User-Agent not allowed',
            'suspicious_pattern': 'Access denied: Suspicious User-Agent detected',
        }
        
        message = error_messages.get(reason, 'Access denied: User-Agent check failed')
        
        return JSONResponse(
            status_code=status.HTTP_403_FORBIDDEN,
            content={
                "code": 403,
                "message": message,
                "data": {
                    "reason": reason,
                    "timestamp": int(time.time()),
                    "hint": "请使用合法的浏览器或应用访问此API"
                }
            },
            headers={
                "X-Content-Type-Options": "nosniff",
                "X-Frame-Options": "DENY"
            }
        )
    
    def get_configuration_info(self) -> dict:
        """获取中间件配置信息（用于调试和监控）"""
        return {
            "enabled": self.config['enabled'],
            "strict_mode": self.config['strict_mode'],
            "block_empty_ua": self.config['block_empty_ua'],
            "case_sensitive": self.config['case_sensitive'],
            "blocked_patterns_count": len(self.blocked_patterns),
            "allowed_patterns_count": len(self.allowed_patterns),
            "suspicious_patterns_count": len(self.suspicious_patterns),
            "check_patterns": self.config['check_patterns'],
            "exempt_patterns": self.config['exempt_patterns'],
            "check_methods": list(self.config['check_methods']),
            "exempt_methods": list(self.config['exempt_methods']),
        }